![]() |
![]() |
|
Der Versuch, diesen Programmcode zu kompilieren, endet jedoch in einem Fiasko, denn die Felder center und countCircles kann der VB-Compiler nicht erkennen und verweigert deswegen den Zugriff. Der Grund hierfür ist recht einfach: Diese Felder sind in der Basisklasse Circle Private deklariert, und private Member sind grundsätzlich nur in der deklarierenden Klasse bekannt. Obwohl aus objektorientierter Sicht ein Objekt vom Typ GraphicCircle auch gleichzeitig als ein Objekt vom Typ Circle angesehen wird, kann die strikte Kapselung einer privaten Variablen durch die Vererbung nicht aufgebrochen werden. Nur der Code in der Klasse Circle hat Zugriff auf die privaten Klassenmitglieder. 6.2.3 Der Zugriffsmodifizierer »Protected«
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member, die Protected deklariert sind, verhalten sich ähnlich wie Private deklarierte: Sie verhindern den unzulässigen Zugriff von außerhalb, garantieren andererseits jedoch, dass aus einer abgeleiteten Klasse darauf zugegriffen werden kann. |
Diese Erkenntnis führt zu einem Umdenken bei der Implementierung einer Klasse: Muss davon ausgegangen werden, dass die Klasse als Basisklasse ihre Dienste zur Verfügung stellt, sind alle privaten Member, die einer abgeleiteten Klasse zur Verfügung stehen sollen, Protected zu deklarieren. Daher müssen wir in der Klasse Circle noch folgende Änderungen vornehmen:
| Protected center As Point |
| Protected dblRadius As Double |
| Protected Shared intCountCircles As Integer |
Erst jetzt ist die Klasse Circle tatsächlich vererbungsfähig, und der VB-Compiler wird keinen Fehler mehr melden.
Wir wollen nun die Implementierung in Main testen, indem wir ein Objekt des Typs GraphicCircle erzeugen und uns den Stand des Objektzählers, der aus der Circle-Klasse geerbt wird, an der Konsole ausgeben lassen. Der Code dazu lautet:
| Sub Main() |
| Dim gc As GraphicCircle = New GraphicCircle() |
| Console.WriteLine("Anzahl der Kreise = {0}", _ |
| GraphicCircle.CountCircles) |
| End Sub |
Völlig unerwartet werden wir mit einer Situation konfrontiert, die wir nicht erwartet haben. Mit
| Anzahl der Kreise = 2 |
wird uns suggeriert, wir hätten zwei Kreisobjekte erzeugt, obwohl wir doch tatsächlich nur einmal den New-Operator benutzt haben und sich folgerichtig tatsächlich auch nur ein konkretes Objekt im Speicher befinden kann.
Das Ergebnis ist falsch und beruht auf der bisher noch nicht berücksichtigten Aufrufverkettung zwischen den Sub- und den Basisklassenkonstruktoren. Konstruktoren werden nicht vererbt und müssen deshalb – falls erforderlich – in jeder abgeleiteten Klasse neu definiert werden. Dennoch kommt den Konstruktoren einer Basisklasse eine entscheidende Bedeutung zu.
|
Bei der Initialisierung des Objekts vom Typ einer abgeleiteten Klasse wird in jedem Fall zuerst ein Basisklassenkonstruktor aufgerufen. Es kommt zu einer Top-down-Verkettung der Konstruktoren, angefangen bei der obersten Basisklasse bis hinunter zu der Klasse, deren Konstruktor ausgeführt werden muss. |
Die Verkettung der Konstruktoraufrufe dient dazu, zunächst die geerbten Komponenten der Basisklasse zu initialisieren. Erst danach wird der Konstruktor der Subklasse ausgeführt, der eigene Initialisierungen vornehmen kann und gegebenenfalls auch die Vorinitialisierung der geerbten Komponenten an die Bedürfnisse der abgeleiteten Klasse anpasst. Standardmäßig wird implizit immer zuerst der parameterlose Konstruktor der Basisklasse aufgerufen.
Wir wollen uns das an einem einfachen Beispiel verdeutlichen.
| ' -------------------------------------------------------------- |
| ' Beispiel: ...\Kapitel 6\KonstruktorVerkettung |
| ' -------------------------------------------------------------- |
| ' ----- Basisklasse ----- |
| Class BaseClass |
| ' Standardkonstruktor |
| Public Sub New() |
| Console.WriteLine("Konstruktor in 'BaseClass'") |
| End Sub |
| End Class |
| ' ----- SubClass1 leitet BaseClass ab ----- |
| Class SubClass1 |
| Inherits BaseClass |
| ' Standardkonstruktor |
| Public Sub New() |
| Console.WriteLine("Konstruktor in 'SubClass1'") |
| End Sub |
| End Class |
| ' ----- SubClass2 leitet SubClass1 ab ----- |
| Class SubClass2 |
| Inherits SubClass1 |
| ' Standardkonstruktor |
| Public Sub New() |
| Console.WriteLine("Konstruktor (parameterlos) in 'SubClass2'") |
| End Sub |
| ' parameterbehafteter Konstruktor |
| Public Sub New(ByVal x As Integer) |
| Console.WriteLine("Konstruktor (parametrisiert) in 'SubClass2'") |
| End Sub |
| End Class |
Die Klasse BaseClass dient als Basisklasse der Klasse SubClass1, die ihrerseits wiederum Basisklasse der Klasse SubClass2 ist. In allen Klassen ist der parameterlose Konstruktor implementiert, in SubClass2 zusätzlich noch ein parametrisierter. Um die Verkettung der Konstruktoren zu testen, werden zwei Objekte des Typs SubClass2 erzeugt: einmal durch Aufruf des parameterlosen Konstruktors, im zweiten Fall mit dem Aufruf des parametrisierten.
| Module Module1 |
| Sub Main() |
| Dim obj1 As SubClass2 = New SubClass2() |
| Console.WriteLine("---------------------------") |
| Dim obj2 As SubClass2 = New SubClass2(2) |
| Console.ReadLine() |
| End Sub |
| End Module |
Die Ausgabe an der Konsole bestätigt die Aussage, dass implizit zuerst der parameterlose Basisklassenkonstruktor aufgerufen wird, danach der Konstruktor des eigentlichen Objekts.
| Konstruktor in 'BaseClass' |
| Konstruktor in 'SubClass1' |
| Konstruktor (parameterlos) in 'SubClass2' |
| ----------------------------- |
| Konstruktor in 'BaseClass' |
| Konstruktor in 'SubClass1' |
| Konstruktor (parametrisiert) in 'SubClass2' |
Dabei ist es bedeutungslos, ob das Objekt einer abgeleiteten Klasse mit oder ohne Argumente instanziiert wird, denn der implizite Aufruf landet immer beim parameterlosen Konstruktor der Basisklasse. Wenn wir präzise sein wollen, müssen wir sogar sagen, dass die Aufrufverkettung nicht in BaseClass beginnt, sondern tatsächlich in Object, da diese Klasse oberste Basisklasse aller .NET-Klassen ist und als einzige Klasse selbst keine Basisklasse besitzt.

Hier klicken, um das Bild zu Vergrößern
Abbildung 6.3 Aufrufverkettung der Konstruktoren
Die Konstruktorverkettung hat maßgeblichen Einfluss auf die Modellierung einer Klasse, die parametrisierte Konstruktoren enthält. Eine konstruktorlose Klasse enthält grundsätzlich immer den impliziten parameterlosen Konstruktor. Ergänzt man aber eine Klassendefinition um einen parametrisierten, existiert der implizite parameterlose nicht mehr.
Wird das Objekt einer ableitenden Klasse erzeugt, kommt es – wie wir oben gesehen haben – zum Aufruf des parameterlosen Konstruktors der Basisklasse. Sie sollten sich dieses Effekts bewusst sein, wenn Sie eine ableitbare Klasse entwickeln und parametrisierte Konstruktoren hinzufügen. Im Bedarfsfall ist der parameterlose Konstruktor zu implementieren – selbst dann, wenn er keinen Programmcode enthält.
Nun kann auch das scheinbar unsinnige Ergebnis des Objektzählers des vorhergehenden Abschnitts erklärt werden, der bei der Instanziierung eines Objekts vom Typ GraphicCircle behauptete, zwei Kreisobjekte würden vorliegen, obwohl es doch nachweislich nur eins war. Durch die Konstruktorverkettung wird – so wie es die noch unveränderte Implementierung der Konstruktoren vorgibt – zunächst der parameterlose Konstruktor der Basisklasse Circle aufgerufen, danach der der Klasse GraphicCircle. In beiden wird der Objektzähler erhöht und führt letztendlich zu einem falschen Zählerstand.
Die Ursache des Problems ist die Duplizität der Konstruktorimplementierung in beiden Klassen. Codewiederholung wird in der Programmierung vermieden, indem der wiederholt aufzurufende Code in einer eigenen Prozedur ausgelagert wird, die immer dann aufgerufen wird, wenn sie benötigt wird. Sehr ähnlich kann das Problem auch bei den Konstruktoren gelöst werden: Aus der Subklasse heraus wird ein bestimmter Konstruktor der Basisklasse aufgerufen.
Um eine Methode der Basisklasse aufzurufen – und als eine solche werden natürlich auch die Konstruktoren angesehen –, bietet Visual Basic 2005 das Schlüsselwort MyBase an.
Ersetzen wir den parameterlosen Konstruktor der Subklasse GraphicCircle durch
| ' Konstruktor der Klasse GraphicCircle |
| Public Sub New() |
| MyBase.New() |
| End Sub |
und schreiben danach eine kleine Testroutine, wird die Ausgabe des Objektzählers den korrekten Stand wiedergeben.
Konstruktoren können viele Anweisungen enthalten. Der Basisklassenkonstruktor wird grundsätzlich immer vor allen anderen Anweisungen als Erstes aufgerufen. Da spielt es keine Rolle, ob der Aufruf implizit erfolgt oder nicht. Mit anderen Worten: Wollen Sie per Code einen Basisklassenkonstruktor explizit aufrufen, muss diese Anweisung immer als erste in der Konstruktorimplementierung stehen.
Sehen wir uns nun abschließend die überarbeitete Fassung der GraphicCircle-Konstruktoren an:
| Public Sub New() |
| MyBase.New() |
| End Sub |
| Public Sub New(ByVal radius As Single) |
| MyBase.New(radius) |
| End Sub |
| Public Sub New(ByVal radius As Single, ByVal x As Single, _ |
| ByVal y As Single) |
| MyBase.New(radius, x, y) |
| End Sub |
| Public Sub New(ByVal radius As Double, ByVal pt As Point) |
| MyBase.New(radius, pt) |
| End Sub |
Beim parameterlosen Konstruktor könnten wir auf MyBase verzichten, weil der Aufruf auch ohne diese Angabe an den parameterlosen Basisklassenkonstruktor weitergeleitet wird.
Schreiben wir jetzt eine Testroutine, z. B.:
| Dim gc As GraphicCircle = New GraphicCircle() |
| Console.WriteLine("Anzahl der Kreise = {0}", _ |
| GraphicCircle.CountCircles) |
wird die Ausgabe des Objektzählers tatsächlich den korrekten Stand wiedergeben.
Mit MyBase kann auf alle öffentlichen Mitglieder der direkten Basisklasse Bezug genommen werden. Dabei gilt, dass die Methode der Basisklasse durchaus eine Methode sein kann, welche die Basisklasse selbst geerbt hat, also aus Sicht der MyBase-implementierenden Subklasse aus einer indirekten Basisklasse stammt. Ein umgeleiteter Aufruf an eine indirekte Basisklasse mit
| MyBase.MyBase.TestProc() |
ist nicht gestattet. Ist die Methode parametrisiert, müssen die Argumente der Definition in der Parameterliste entsprechen.
Beim Einsatz von MyBase sind einige Dinge zu beachten:
| Mit MyBase kann nur auf die in der direkten Basisklasse öffentlich definierten sowie auf die geerbten Member zugegriffen werden. Der Zugriff erfolgt mit der allgemein üblichen Punktnotation und setzt die korrekte Angabe der Parameterliste voraus. |
| MyBase ist eine implizite Referenz und kann daher auch nicht einer Variablen zugewiesen werden oder als Übergabeargument an eine Prozedur dienen. |
| Da mein MyBase nur eine implizite Referenz ist und kein Objekt im eigentlichen Sinne, ist die Benutzung in einem Vergleich mit dem Schlüsselwort Is nicht zulässig. |
Der parameterlose Konstruktor der Basisklasse wird immer dann automatisch aufgerufen, wenn in der ersten Anweisung des aufgerufenen Konstruktors der Subklasse mit MyBase.New([Parameterliste]) kein bestimmter Aufruf eines beliebigen Basisklassenkonstruktors erzwungen wird.
Diesem Umstand muss beim Design einer Klasse, die als Basisklasse abgeleitet werden kann, besondere Beachtung geschenkt werden. Enthält nämlich eine ableitbare Klasse parametrisierte Konstruktoren, ersetzen diese den impliziten, parameterlosen Standardkonstruktor. Er existiert schlichtweg nicht mehr – es sei denn, er ist ebenfalls explizit implementiert. Daher gilt die folgende, wichtige Regel:
|
Werden in einer ableitbaren Klasse parameterbehaftete Konstruktoren implementiert, sollte der parameterlose Konstruktor selbst dann explizit codiert werden, wenn er keinen Code enthält. |
Halten Sie sich nicht an diese Regel, führt das zu einer Fehlermeldung in der abgeleiteten Klasse, wenn die Existenz des Standardkonstruktors vorausgesetzt wird.
Konstruktoren dienen der Initialisierung eines Objekts, die Finalize-Methode wird aufgerufen, wenn der Garbage Collector auf ein Objekt stößt, das im Programmcode nicht weiter benutzt wird.
Der verkettete Konstruktoraufruf in einer Klassenhierarchie findet in ähnlicher Weise auch statt, wenn der Garbage Collector bei seinen Aufräumarbeiten auf die Finalize-Methode in einer Subklasse stößt. Der Ablauf der Verkettung spielt sich allerdings in umgekehrter Reihenfolge ab: Zuerst werden die Aufräumarbeiten in der Subklasse geleistet, danach die in der direkten Basisklasse. Diese Bottom-up-Verkettung unterliegt allerdings keinem automatischen Mechanismus, sondern muss explizit gesteuert werden.
| Protected Overrides Sub Finalize() |
| 'Anweisungen |
| MyBase.Finalize |
| End Sub |
Erst die letzte Anweisung in der Finalize-Methode sollte den Aufruf der gleichnamigen Methode in der Basisklasse bewirken. In der folgenden Abbildung 6.4 ist die Aufrufverkettung anschaulich nachzuvollziehen.

Hier klicken, um das Bild zu Vergrößern
Abbildung 6.4 Die Aufrufverkettung von Finalisierungsmethoden
Der Gültigkeitsbereich von Finalize ist Protected, d. h., sie kann nur aus der eigenen Klasse heraus oder aus einer abgeleiteten Klasse heraus aufgerufen werden. Definiert ist Finalize in der Klasse Object in der Weise, dass die Methode überschrieben werden darf. Wie Sie später noch sehen werden, muss eine Methode, die eine andere gleichnamige Methode in der Basisklasse überschreibt, mit Overrides gekennzeichnet werden.
| Konstruktoren werden grundsätzlich nicht von der Basisklasse an die Subklasse vererbt. |
| Private deklarierte Mitglieder in einer Basisklasse sind in der abgeleiteten Klasse nicht sichtbar. Um das Prinzip der Kapselung nicht aufzubrechen und diese Klassenmitglieder zu vererben, muss das entsprechende Mitglied in der Basisklasse Protected deklariert werden. |
| Sub- und Basisklassenkonstruktor hängen in der Weise voneinander ab, dass zuerst der Basisklassenkonstruktor und danach der Konstruktor der abgeleiteten Klasse ausgeführt wird. |
| Wenn nicht anders angegeben, wird in einer abgeleiteten Klasse implizit zuerst der parameterlose Standardkonstruktor der Basisklasse aufgerufen, bevor die erste Anweisung im Subklassenkonstruktor ausgeführt wird. |
| Mit dem Schlüsselwort MyBase kann die implizite Konstruktorverkettung zwischen zwei in einer Vererbungsbeziehung stehenden Klassen durch eine benutzerdefinierte ersetzt werden. |
| Stößt der Garbage Collector bei seinen Aufräumarbeiten im freizugebenden Objekt auf einen Destruktor, werden alle Destruktoren in der Vererbungslinie durchlaufen, beginnend mit dem des aktuellen Objekts. |
| Der Aufruf des Basisklassendestruktors erfolgt erst, nachdem alle Anweisungen des Destruktors der Subklasse ausgeführt worden sind. |
Fassen wir an dieser Stelle noch einmal alle Ergänzungen und Änderungen des Projekts CircleApplication zusammen.
| ' ---------------------------------------------------------- |
| ' Beispiel: ...\Kapitel 6\CircleApplication_4 |
| ' ---------------------------------------------------------- |
| Public Class Circle |
| Implements IDisposable |
| ' ---------- Instanzfelder -------------- |
| Private bolReduced As Boolean |
| Protected center As Point |
| Protected dblRadius As Double |
| ' --------- statische Felder -------------- |
| Protected Shared intCountCircles As Integer |
| ... |
| End Class |
| Public Class GraphicCircle |
| Inherits Circle |
| ' Konstruktoren |
| Public Sub New() |
| End Sub |
| Public Sub New(ByVal radius As Double) |
| MyBase.New(radius) |
| End Sub |
| Public Sub New(ByVal radius As Double, _ |
| ByVal x As Integer, ByVal y As Integer) |
| MyBase.New(radius, x, y) |
| End Sub |
| Public Sub New(ByVal radius As Double, ByVal pt As Point) |
| MyBase.New(radius, pt) |
| End Sub |
| ' Typspezifische Methode |
| Public Sub Draw() |
| Console.WriteLine("Der Kreis wird gezeichnet") |
| End Sub |
| End Class |
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken.
Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die
gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich
geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung,
Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.